/******************************************************************************* * Copyright (c) 2014 Moritz Eysholdt and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Moritz Eysholdt <moritz.eysholdt@itemis.de> - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.junit4.runner; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.junit.runner.Description; import org.junit.runners.Parameterized; /** * This class matches JUnit's {@link Description} against a string. * * See {@link #create(Class, String)} for details. */ public abstract class DescriptionMatcher { private static class CompositeMatcher extends DescriptionMatcher { private final List<DescriptionMatcher> fMatchers; public CompositeMatcher(List<DescriptionMatcher> matchers) { fMatchers= matchers; } @Override public boolean matches(Description description) { for (DescriptionMatcher matcher : fMatchers) if (matcher.matches(description)) return true; return false; } @Override public String toString() { return fMatchers.toString(); } } private static class ExactMatcher extends DescriptionMatcher { private final String fDisplayName; public ExactMatcher(String className) { fDisplayName= className; } public ExactMatcher(String className, String testName) { // see org.junit.runner.Description.formatDisplayName(String, String) fDisplayName= String.format("%s(%s)", testName, className); //$NON-NLS-1$ } @Override public boolean matches(Description description) { return fDisplayName.equals(description.getDisplayName()); } @Override public String toString() { return String.format("{%s:fDisplayName=%s]", getClass().getSimpleName(), fDisplayName); //$NON-NLS-1$ } } /** * This class extracts the leading chars from {@link Description#getMethodName()} which are a * valid Java identifier. If this identifier equals this class' identifier, the Description is * matched. * * Please be aware that {@link Description#getMethodName()} can be any value a JUnit runner has * computed. It is not necessarily a valid method name. For example, {@link Parameterized} uses * the format 'methodname[i]', with 'i' being the row index in the table of test data. */ private static class LeadingIdentifierMatcher extends DescriptionMatcher { private final String fClassName; private final String fLeadingIdentifier; public LeadingIdentifierMatcher(String className, String leadingIdentifier) { super(); fClassName= className; fLeadingIdentifier= leadingIdentifier; } @Override public boolean matches(Description description) { String className= description.getClassName(); if (fClassName.equals(className)) { String methodName= description.getMethodName(); if (methodName != null) { return fLeadingIdentifier.equals(extractLeadingIdentifier(methodName)); } } return false; } @Override public String toString() { return String.format("{%s:fClassName=%s,fLeadingIdentifier=%s]", getClass().getSimpleName(), fClassName, fLeadingIdentifier); //$NON-NLS-1$ } } /** * Creates a matcher object that can decide for {@link Description}s whether they match the * supplied 'matchString' or not. * * Several strategies for matching are applied: * <ul> * <li>if 'matchString' equals {@link Description#getDisplayName()}, it's always a match</li> * <li>if 'matchString' has the format foo(bar), which is JUnit's format, it tries to match * methods base on leading identifiers. See {@link LeadingIdentifierMatcher}.</li> * <li>if 'matchString' does not have the format foo(bar), it also tries to match Descriptions * that equal clazz(matchString), with 'clazz' being this method's parameter. Furthermore, if * 'matchString' is a Java identifier, it matches Descriptions via leading identifiers. See * {@link LeadingIdentifierMatcher}</li> * </ul> * * @param clazz A class that is used when 'matchString' does not have the format * methodName(className). * * @param matchString A string to match JUnit's {@link Description}s against. * * @return A matcher object. */ public static DescriptionMatcher create(Class<?> clazz, String matchString) { List<DescriptionMatcher> matchers= new ArrayList<DescriptionMatcher>(); matchers.add(new ExactMatcher(matchString)); Matcher parsed= METHOD_AND_CLASS_NAME_PATTERN.matcher(matchString); if (parsed.matches()) { String className= parsed.group(2); String testName= parsed.group(1); if (testName.equals(extractLeadingIdentifier(testName))) matchers.add(new LeadingIdentifierMatcher(className, testName)); } else { String className= clazz.getName(); if (!className.equals(matchString)) { matchers.add(new ExactMatcher(className, matchString)); if (matchString.equals(extractLeadingIdentifier(matchString))) matchers.add(new LeadingIdentifierMatcher(className, matchString)); } } return new CompositeMatcher(matchers); } private static String extractLeadingIdentifier(String string) { if (string.length() == 0) return null; if (!Character.isJavaIdentifierStart(string.charAt(0))) return null; for (int i= 1; i < string.length(); i++) { if (!Character.isJavaIdentifierPart(string.charAt(i))) { return string.substring(0, i); } } return string; } // see org.junit.runner.Description.METHOD_AND_CLASS_NAME_PATTERN private static final Pattern METHOD_AND_CLASS_NAME_PATTERN= Pattern.compile("(.*)\\((.*)\\)"); //$NON-NLS-1$ public abstract boolean matches(Description description); }